import typing

from starlette.authentication import AuthenticationBackend, AuthenticationError, AuthCredentials, SimpleUser, BaseUser
from starlette.requests import HTTPConnection
from fastapi import Response

from common.pkg.jwt_util import decode_jwt_token
from common.utils.resp import JsonResponse, RC


class JWTAuthBackend(AuthenticationBackend):
    def __init__(self, white_path_list: typing.List[str], white_path_prefix_tuple: typing.Tuple[str]):
        self.white_path_list = white_path_list or []
        self.white_path_prefix_tuple = white_path_prefix_tuple or ()

    async def authenticate(self, conn: HTTPConnection) -> typing.Optional[typing.Tuple["AuthCredentials", "BaseUser"]]:
        path = conn.url.path
        if path in self.white_path_list or (self.white_path_prefix_tuple and path.startswith(self.white_path_prefix_tuple)):
            return None
        if "Authorization" not in conn.headers:
            raise AuthenticationError("缺失认证参数")
        auth = conn.headers.get("Authorization")
        if not auth.startswith("Bearer "):
            raise AuthenticationError("认证参数格式错误")
        _, token = auth.split()
        ok, payload = decode_jwt_token(token,
                                       conn.app.settings.jwt_config.secret,
                                       conn.app.settings.jwt_config.algorithms)
        if not ok:
            raise AuthenticationError("认证失败")
        conn.state.user_id = payload.get("user_id")
        return AuthCredentials(scopes=["authenticated"]), SimpleUser(payload.get("username"))


def on_error(conn: HTTPConnection, exc: Exception) -> Response:
    return JsonResponse(code=RC.AUTH_FAILED, msg="认证失败", exception_track=exc).to_response()

